"""Frontend for the whole shebang.

Requires:
  CherryPy:      http://www.cherrypy.org/
  Mako:          http://www.makotemplates.org/
  SQLAlchemy:    http://www.sqlalchemy.org/
  ChartDirector: http://www.advsofteng.com/
"""

__author__ = "Gabriel Taubman (gtaubman@mac.com)"

from auth import AuthController, require, admin
from constants import DATABASE_FILE, MAX_PLAYER_FILE_SIZE
from html_utils import Username
from dbutils import *
from mako.template import Template
from mako.lookup import TemplateLookup
from pychartdir import *
import cherrypy
import sqlalchemy
import models
import time

mylookup = TemplateLookup(directories=['static/templates/'])
Session = models.SessionMaker(DATABASE_FILE)

def ToChartTime(d):
  """Converts from a python datetime to a chart director time."""
  return chartTime(d.year, d.month, d.day, d.hour, d.minute, d.second)

class User:
  @cherrypy.expose
  def set_player_config(self, username = None, **kwargs):
    # Store which users to enable.
    to_enable = []

    # Unpack which should be enabled.
    for key, value, in kwargs.iteritems():
      if key == "enabled":
        if type(value) == str:
          to_enable.append((value.split("|")))
        else:
          for v in value:
            to_enable.append((v.split("|")))

    session = Session()
    user = session.query(models.User).filter(models.User.username == username).one()
    if not user:
      raise cherrypy.HTTPRedirect("/")

    for player in user.players:
      if [player.name, str(player.version)] in to_enable:
        player.enabled = True
      else:
        player.enabled = False

    session.commit()
    raise cherrypy.HTTPRedirect("/user/" + username)

  @cherrypy.expose
  def default(self, user = None, player_name = None, version = 0):
    session = Session()
    from_page = "/user/%s" % user

    # We show more options if the requested user is the logged in user.
    username = Username()
    if username == user:
      is_us = True
    else:
      is_us = False

    template = mylookup.get_template("user.tpl")

    # Grab the user.
    user = session.query(models.User).filter(models.User.username == user).one()
    if not user:
      return template.render(username = "Unknown",
          user = user,
          error = "No such user",
          from_page = from_page)
    
    # If a player is requested, show a different page.
    if player_name != None:
      player = session.query(models.Player).filter(sqlalchemy.and_(
        models.Player.name == player_name,
        models.Player.owner == user,
        models.Player.version == version)).one()
      if not player:
        # If there are no players, or none with that name, then bounce back to
        # the user's page.
        raise cherrypy.HTTPRedirect("/user/%s" % user.username)

      template = mylookup.get_template("player.tpl")
      return template.render(username = username,
          player = player,
          is_us = is_us,
          from_page = from_page + "/" + player_name)

    return template.render(username = username,
                          is_us = is_us,
                          user = user,
                          players = user.players,
                          from_page = from_page)

  @cherrypy.expose
  def add_player(self, username = None, file_obj = None, player_name = None):
    if not username or not player_name:
      raise cherrypy.HTTPRedirect(username)

    session = Session()

    # Grab this user.
    user = session.query(models.User).filter(models.User.username == username).one()
    if not user:
      return "No known user %s." % username

    # Find out what the version number of this player should be.  This is
    # determined by whether or not the user already has a player with the same.
    version = session.query(models.Player).filter(
        sqlalchemy.and_(models.Player.owner == user,
             models.Player.name == player_name)).count()

    if version != 0:
      # There is a previous version of this player.  Fetch its rank.
      rank = session.query(models.Player).filter(
          sqlalchemy.and_(models.Player.owner_id == user.id,
               models.Player.version == (version - 1),
               models.Player.name == player_name)).value(models.Player.rank)
      print "Rank: " + str(rank)
      rank = float(rank)
    else:
      rank = 0

    # Read the file_obj into memory.
    data = []
    bytes_read = 0
    for line in file_obj.file:
      bytes_read += len(line)
      data.append(line)
      if bytes_read > MAX_PLAYER_FILE_SIZE:
        return "Player File Too Big."

    if bytes_read == 0:
      raise cherrypy.HTTPRedirect(username)

    new_player = CreatePlayer(user, version, player_name,
                              rank, False, file_obj.filename, "".join(data)) 
    user.players.append(new_player)
    session.commit()
    raise cherrypy.HTTPRedirect(username)

class Chart:
  @cherrypy.expose
  def game_duration_plot(self, width = None, height = None, length = 5000):
    if not width or not height:
      return "Must specify width and height"
    width = int(width)
    height = int(height)

    session = Session()

    durations = (session.query(models.Game.match_length).
                 order_by(models.Game.id.desc()).limit(length).all())
    dates = (session.query(models.Game.date_added).
             order_by(models.Game.id.desc()).limit(length).all())
    dates.reverse()
    durations.reverse()

    c = XYChart(width, height)
    c.setPlotArea(50, 30, width - 70, height - 80, 0xffffc8)
    c.addTitle("Game Duration")
    c.yAxis().setTitle("Game Duration in Seconds")
    values = [d[0] for d in durations]
    layer = c.addLineLayer(values, 0x000000)
    layer.setLineWidth(1)
    layer.setXData([ToChartTime(d[0]) for d in dates])

    cherrypy.response.headers['Content-Type']= 'image/png'
    return c.makeChart2(PNG)

  @cherrypy.expose
  def win_average(self,
                  width = 400,
                  height = 800,
                  username = None,
                  playername = None,
                  version = 0,
                  average_over = 10):
    if not (width and height and username and playername and average_over):
      return ("Must specify width, height, username, playername, and average_over.")
    width = int(width)
    height = int(height)
    version = int(version)
    average_over = int(average_over)

    session = Session()

    # Get the user.
    user = session.query(models.User).filter(models.User.username == username).one()
    if not user:
      return "Not a valid user."

    # Get the player.
    player = session.query(models.Player).filter(sqlalchemy.and_(
        models.Player.owner == user,
        models.Player.name == playername,
        models.Player.version == version)).one()
    if not player:
      return "Not a valid player."

    # Compute the player's win/loss list.
    before = time.time()

    my_score = sqlalchemy.orm.aliased(models.Score)
    other_score = sqlalchemy.orm.aliased(models.Score)

    win_loss = []
    dates = []
    for game_id, date_added, player_in_game_score, max_in_game_score in (
      session.query(models.Game.id,
                    models.Game.date_added,
                    my_score.score,
                    sqlalchemy.func.max(other_score.score))
      .filter(models.Game.id == my_score.game_id)
      .filter(models.Game.id == other_score.game_id)
      .filter(my_score.player_id == player.id)
      .group_by(models.Game.id).group_by(my_score.score)):
      if player_in_game_score == max_in_game_score:
        win_loss.append(1)
      else:
        win_loss.append(0)
      dates.append(date_added)

    c = XYChart(width, height)
    c.setPlotArea(50, 30, width - 70, height - 80, 0xffffc8)
    c.addTitle(
        "Win Percentage Averaged over %d Games" %
        (average_over))
    c.yAxis().setLabelFormat("{value}%")

    dates = [ToChartTime(d) for d in dates]
    scores = []
    for start in range(len(win_loss)):
      tally = (sum([win_loss[j] for j in
        range(start, min(len(win_loss), start + average_over))]) / float(average_over) * 100.0)
      scores.append(tally)

    l = max(0, len(scores) - average_over)
    del scores[l:]
    del dates[l:]

    layer = c.addLineLayer(scores, 0x000000)
    layer.setXData(dates)
    cherrypy.response.headers['Content-Type']= 'image/png'
    return c.makeChart2(PNG)


class Frontend:
  _cp_config = {
    'tools.sessions.on': True,
    'tools.auth.on': True
  }

  auth = AuthController()
  user = User()
  chart = Chart()

  @cherrypy.expose
  def add_user(self, firstname, lastname, username, email, password):
    session = Session()
    try:
      user = CreateUser(firstname, lastname, username, email, password, False)
      session.add(user)
      session.commit()
    except sqlalchemy.exc.IntegrityError:
      return "Someone already exists with the username %s." % username
    return "You're in %s!" % lastname


  @cherrypy.expose
  def index(self):
    session = Session()

    # Show some statistics.
    stats = []
    stats.append("%d users." % session.query(models.User).count())
    stats.append("%d players." % session.query(models.Player).count())
    stats.append("%d games played." % session.query(models.Game).count())

    # See if we're logged in.
    username = Username()

    # Get a sample of users.
    users = session.query(models.User).limit(10)

    template = mylookup.get_template("homepage.tpl")
    return template.render(username = username,
                           stats = stats,
                           users = users)

if __name__ == "__main__":
  f = Frontend()
  cherrypy.config.update({'server.socket_host': '0.0.0.0'}) 
  cherrypy.tree.mount(f, "/", "frontend.conf")

  if hasattr(cherrypy.engine, 'signal_handler'):
    cherrypy.engine.signal_handler.subscribe()

  cherrypy.engine.start()
